package com.aloda.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.excel.EasyExcel;
import com.aloda.annotation.RateLimit;
import com.aloda.cache.RedisUtils;
import com.aloda.constant.ErrorCode;
import com.aloda.constant.ExceptionConstant;
import com.aloda.constant.RedisConstant;
import com.aloda.constant.UserConstant;
import com.aloda.context.UserBaseContext;
import com.aloda.dto.question.QuestionAnswerDTO;
import com.aloda.dto.userAnswer.UserAnswerCacheDTO;
import com.aloda.entity.App;
import com.aloda.entity.User;
import com.aloda.entity.UserAnswer;
import com.aloda.enums.ReviewStatusEnum;
import com.aloda.enums.UserStatus;
import com.aloda.exception.BaseException;
import com.aloda.exception.WarnException;
import com.aloda.mapper.AppMapper;
import com.aloda.mapper.UserAnswerMapper;
import com.aloda.mapper.UserMapper;
import com.aloda.query.AnswerRecordPageDTO;
import com.aloda.query.PageQuery;
import com.aloda.query.UserAnswerPageDTO;
import com.aloda.result.PageResult;
import com.aloda.scoring.ScoringStrategyExecutor;
import com.aloda.service.UserAnswerService;
import com.aloda.vo.userAnswer.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.io.IOException;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author aloda
 * @description 针对表【user_answer(用户答题记录)】的数据库操作Service实现
 * @createDate 2025-03-05 16:27:38
 */
@Service
public class UserAnswerServiceImpl extends ServiceImpl<UserAnswerMapper, UserAnswer> implements UserAnswerService {

    @Resource
    private UserAnswerMapper userAnswerMapper;

    @Resource
    private AppMapper appMapper;

    @Resource
    private UserMapper userMapper;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private ScoringStrategyExecutor scoringStrategyExecutor;

    @Resource
    private RedissonClient redissonClient;

    @Override
    public PageResult<UserAnswerPageVO> pageQuery(UserAnswerPageDTO userAnswerPageDTO) {
        Page<UserAnswer> page = userAnswerPageDTO.toMpPageDefaultSortByUpdateTimeDesc();
        LambdaQueryWrapper<UserAnswer> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.like(userAnswerPageDTO.getResultName() != null, UserAnswer::getResultName, userAnswerPageDTO.getResultName())
                .like(userAnswerPageDTO.getUserAccount() != null, UserAnswer::getUserAccount, userAnswerPageDTO.getUserAccount())
                .eq(userAnswerPageDTO.getAppId() != null, UserAnswer::getAppId, userAnswerPageDTO.getAppId())
                .between(userAnswerPageDTO.getBeginTime() != null && userAnswerPageDTO.getEndTime() != null,
                        UserAnswer::getCreateTime, userAnswerPageDTO.getBeginTime(), userAnswerPageDTO.getEndTime());
        userAnswerMapper.selectPage(page, lambdaQueryWrapper);
        return PageResult.of(page, UserAnswerPageVO.class);
    }

    @Override
    public void exportToExcel(HttpServletResponse response, List<Long> ids) {
        try {
            // 设置响应头
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode("用户答题记录信息表", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

            // 查询所有用户答题记录 如果ids为空查询所有信息，否则查询对应信息
            LambdaQueryWrapper<UserAnswer> queryWrapper = new LambdaQueryWrapper<>();
            List<UserAnswer> list;
            if (ids.isEmpty()) {
                list = userAnswerMapper.selectList(queryWrapper);
            } else {
                list = userAnswerMapper.selectBatchIds(ids);
            }

            // 转换为导出VO
            List<UserAnswerExportVO> exportVOList = list.stream().map(userAnswer -> BeanUtil.copyProperties(userAnswer, UserAnswerExportVO.class))
                    .collect(Collectors.toList());

            // 使用EasyExcel导出
            EasyExcel.write(response.getOutputStream(), UserAnswerExportVO.class)
                    .sheet("用户答题记录信息")
                    .doWrite(exportVOList);

        } catch (IOException e) {
            throw new RuntimeException(ExceptionConstant.EXPORT_EXCEL_ERROR, e);
        }
    }

    /**
     * 新增用户回答记录
     *
     * @param questionAnswerDTO
     * @return
     */
    @Override
    @RateLimit(limit = 3, period = 10)
    public Long addUserAnswer(QuestionAnswerDTO questionAnswerDTO) {
        // 1.校验参数
        Long appId = questionAnswerDTO.getAppId();
        if (appId == null) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        // 对答案数组进行校验
        if (questionAnswerDTO.getAnswerList().isEmpty()) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        // 2.查询app
        App app = appMapper.selectById(appId);
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        if (!app.getReviewStatus().equals(ReviewStatusEnum.PASS.getValue())) {
            throw new BaseException(ExceptionConstant.APP_NOT_REVIEW);
        }

        // 3.先查询是否有缓存的评分结果，如果有直接使用缓存的评分结果
        // 构建该题目选项对应的key
        String key = RedisConstant.APP_USER_ANSWER
                + questionAnswerDTO.getAppId().toString()
                + ":"
                + DigestUtils.md5DigestAsHex(questionAnswerDTO.getAnswerList().toString().getBytes());
        UserAnswerCacheDTO userAnswerCacheDTO = redisUtils.getObjectFromCache(key, UserAnswerCacheDTO.class);
        if (BeanUtil.isNotEmpty(userAnswerCacheDTO)) {
            UserAnswer userAnswer = BeanUtil.copyProperties(userAnswerCacheDTO, UserAnswer.class);
            userAnswer.setUserId(UserBaseContext.getUser().getId());
            userAnswer.setUserAccount(UserBaseContext.getUser().getUserAccount());
            userAnswer.setCreateTime(LocalDateTime.now());
            userAnswer.setUpdateTime(LocalDateTime.now());
            userAnswerMapper.insert(userAnswer);
            return userAnswer.getId();
        }

        // 获取分布式锁  解决缓存击穿
        String lockKey = key + ":answer-lock";
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试获取锁，等待 20 秒，自动释放时间为 60 秒
            if (lock.tryLock(20, 60, TimeUnit.SECONDS)) {
                // 再次检查缓存，避免在等待锁的过程中其他线程已经更新了缓存
                userAnswerCacheDTO = redisUtils.getObjectFromCache(key, UserAnswerCacheDTO.class);
                if (BeanUtil.isNotEmpty(userAnswerCacheDTO)) {
                    UserAnswer userAnswer = BeanUtil.copyProperties(userAnswerCacheDTO, UserAnswer.class);
                    userAnswer.setUserId(UserBaseContext.getUser().getId());
                    userAnswer.setUserAccount(UserBaseContext.getUser().getUserAccount());
                    userAnswer.setCreateTime(LocalDateTime.now());
                    userAnswer.setUpdateTime(LocalDateTime.now());
                    userAnswerMapper.insert(userAnswer);
                    return userAnswer.getId();
                }

                // 4.缓存中没有评分结果，则进行评分
                UserAnswer userAnswer;
                try {
                    userAnswer = scoringStrategyExecutor.doScore(questionAnswerDTO.getAnswerList(), app);
                    // 缓存答题结果
                    UserAnswerCacheDTO newUserAnswerCache = BeanUtil.copyProperties(userAnswer, UserAnswerCacheDTO.class);
                    // 生成一个随机的过期时间，范围在1 - 3小时之间 解决缓存雪崩
                    int randomExpireTime = RandomUtil.randomInt(RedisConstant.APP_USER_ANSWER_TIME[0],
                            RedisConstant.APP_USER_ANSWER_TIME[1]);
                    redisUtils.saveObjectToCache(key, newUserAnswerCache, randomExpireTime, TimeUnit.HOURS);

                    userAnswer.setUserId(UserBaseContext.getUser().getId());
                    userAnswer.setUserAccount(UserBaseContext.getUser().getUserAccount());
                    userAnswer.setCreateTime(LocalDateTime.now());
                    userAnswer.setUpdateTime(LocalDateTime.now());
                    userAnswerMapper.insert(userAnswer);
                } catch (Exception e) {
                    throw new BaseException(e.getMessage());
                }

                // 返回答题结果id
                return userAnswer.getId();
            } else {
                // 获取锁失败提示系统繁忙
                throw new BaseException(ExceptionConstant.SYSTEM_BUSY);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BaseException(ErrorCode.SYSTEM_ERROR);
        } finally {
            if (lock != null && lock.isLocked()) {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 根据id查询回答记录
     *
     * @param id
     * @return
     */
    @Override
    public UserAnswerResultVO getUserAnswerResultById(Long id) {
        if (id == null) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        UserAnswer userAnswer = userAnswerMapper.selectById(id);
        if (BeanUtil.isEmpty(userAnswer)) {
            throw new BaseException(ExceptionConstant.ANSWER_NOT_EXIST);
        }
        App app = appMapper.selectById(userAnswer.getAppId());
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        // 只有所回答的用户和该答题记录所属应用的创建者可以查看答题记录
        Long userId = UserBaseContext.getUser().getId();
        if (!userAnswer.getUserId().equals(userId) && !app.getUserId().equals(userId)) {
            throw new BaseException(ErrorCode.FORBIDDEN_ERROR);
        }
        User user = userMapper.selectById(userAnswer.getUserId());
        if (BeanUtil.isEmpty(user)) {
            throw new BaseException(ExceptionConstant.USER_NOT_EXIST);
        }
        UserAnswerResultVO userAnswerResultVO = BeanUtil.copyProperties(userAnswer, UserAnswerResultVO.class);
        userAnswerResultVO.setAppId(app.getId());
        userAnswerResultVO.setAppDesc(app.getAppDesc());
        userAnswerResultVO.setAppName(app.getAppName());
        userAnswerResultVO.setAppType(app.getAppType());
        userAnswerResultVO.setUserId(user.getId());
        userAnswerResultVO.setAvatar(user.getAvatar());
        userAnswerResultVO.setNickname(user.getNickname());
        return userAnswerResultVO;
    }

    /**
     * 客户端分页查询答题记录
     *
     * @param answerRecordPageDTO
     * @return
     */
    @Override
    public PageResult<AnswerRecordPageVO> pageQueryByUser(AnswerRecordPageDTO answerRecordPageDTO) {
        if (answerRecordPageDTO.getPageSize() > UserConstant.USER_MAX_PAGE_SIZE) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        Long appId = answerRecordPageDTO.getAppId();
        if (appId == null) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        App app = appMapper.selectById(appId);
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        // 只能是创建用户访问该应用的答题记录
        if (!app.getUserId().equals(UserBaseContext.getUser().getId())) {
            throw new BaseException(ErrorCode.FORBIDDEN_ERROR);
        }
        // 开始分页
        Page<UserAnswer> page = answerRecordPageDTO.toMpPageDefaultSortByUpdateTimeDesc();
        LambdaQueryWrapper<UserAnswer> userAnswerLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userAnswerLambdaQueryWrapper.like(answerRecordPageDTO.getResultName() != null,
                        UserAnswer::getResultName, answerRecordPageDTO.getResultName())
                .eq(UserAnswer::getAppId, answerRecordPageDTO.getAppId());
        userAnswerMapper.selectPage(page, userAnswerLambdaQueryWrapper);
        // 如果分页结果为空直接返回
        if (page.getRecords().isEmpty()) {
            return PageResult.empty(page);
        }
        // 获取所有用户id(非重复)
        Set<Long> userIdSet = page.getRecords().stream().map(UserAnswer::getUserId).collect(Collectors.toSet());
        // 返回封装AnswerRecordPageVO
        Map<Long, List<User>> userIdUserListMap = userMapper.selectBatchIds(userIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));
        return PageResult.of(page, userAnswer -> {
            AnswerRecordPageVO answerRecordPageVO = BeanUtil.copyProperties(userAnswer, AnswerRecordPageVO.class);
            if (userIdUserListMap.containsKey(userAnswer.getUserId())) {
                User user = userIdUserListMap.get(userAnswer.getUserId()).get(0);
                answerRecordPageVO.setNickname(user.getNickname());
            }
            return answerRecordPageVO;
        });
    }

    /**
     * 用户端删除答题记录
     *
     * @param ids
     * @return
     */
    @Override
    public long deleteByIds(List<Long> ids, Long appId) {
        // 校验权限
        checkIsCreateUser(ids, appId);
        if (ids.isEmpty()) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        // 删除对应回答记录
        return userAnswerMapper.deleteByIds(ids);
    }

    /**
     * 用户端导出excel
     *
     * @param ids
     * @param response
     */
    @Override
    public void exportRecordsToExcel(List<Long> ids, HttpServletResponse response, Long appId) {
        // 校验权限
        App app = checkIsCreateUser(ids, appId);
        // 导出表格
        try {
            // 设置响应头
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode("用户答题记录信息表", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

            LambdaQueryWrapper<UserAnswer> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserAnswer::getAppId, appId);
            List<UserAnswer> list;
            if (CollUtil.isEmpty(ids)) {
                // ids为空导出该appId对应的所有答题记录
                list = userAnswerMapper.selectList(queryWrapper);
            } else {
                list = userAnswerMapper.selectBatchIds(ids);
            }
            // 查询对应user
            Set<Long> userIdSet = list.stream().map(UserAnswer::getUserId).collect(Collectors.toSet());
            Map<Long, List<User>> userIdUserListMap = userMapper.selectBatchIds(userIdSet).stream()
                    .collect(Collectors.groupingBy(User::getId));

            // 转换为导出VO
            List<AnswerRecordExportVO> exportVOList = list.stream()
                    .map(userAnswer -> {
                        AnswerRecordExportVO answerRecordExportVO = BeanUtil.copyProperties(userAnswer, AnswerRecordExportVO.class);
                        answerRecordExportVO.setAppName(app.getAppName());
                        if (userIdUserListMap.containsKey(userAnswer.getUserId())) {
                            User user = userIdUserListMap.get(userAnswer.getUserId()).get(0);
                            answerRecordExportVO.setNickname(user.getNickname());
                        }
                        return answerRecordExportVO;
                    })
                    .collect(Collectors.toList());

            // 使用EasyExcel导出
            EasyExcel.write(response.getOutputStream(), AnswerRecordExportVO.class)
                    .sheet("应用" + app.getAppName() + "用户答题记录信息")
                    .doWrite(exportVOList);

        } catch (IOException e) {
            throw new RuntimeException(ExceptionConstant.EXPORT_EXCEL_ERROR, e);
        }
    }

    /**
     * 分页查询用户本人的答题记录
     *
     * @param pageQuery
     * @return
     */
    @Override
    public PageResult<AnswerRecordPageVO> pageQueryUserAR(PageQuery pageQuery) {
        // 1.参数校验
        Long id = UserBaseContext.getUser().getId();
        if (id == null) {
            throw new BaseException(ErrorCode.SYSTEM_ERROR);
        }
        User user = userMapper.selectById(id);
        if (BeanUtil.isEmpty(user)) {
            throw new BaseException(ExceptionConstant.USER_NOT_EXIST);
        }
        if (user.getUserStatus().equals(UserStatus.BANNED)) {
            throw new BaseException(ExceptionConstant.ACCOUNT_EX);
        }
        Page<UserAnswer> userAnswerPage = new Page<>(pageQuery.getCurrentPage(), UserConstant.USER_ANSWER_PAGE_SIZE);
        LambdaQueryWrapper<UserAnswer> userAnswerLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userAnswerLambdaQueryWrapper.eq(UserAnswer::getUserId, id)
                .orderByDesc(UserAnswer::getCreateTime);
        userAnswerMapper.selectPage(userAnswerPage, userAnswerLambdaQueryWrapper);
        return PageResult.of(userAnswerPage, AnswerRecordPageVO.class);
    }

    /**
     * 校验是否为回答记录对应app创建用户
     *
     * @param ids
     */
    private App checkIsCreateUser(List<Long> ids, Long appId) {
        // 1.检查应用是否为该用户创建
        if (appId == null) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        App app = appMapper.selectById(appId);
        if (!app.getUserId().equals(UserBaseContext.getUser().getId())) {
            throw new BaseException(ErrorCode.FORBIDDEN_ERROR);
        }
        // 2.如果是该用户的应用，判断ids对应的回答记录是否属于该应用
        // 2.1.查询ids对应所有回答记录
        if (ids.isEmpty()) {
            return app;
        }
        List<UserAnswer> userAnswerList = userAnswerMapper.selectBatchIds(ids);
        if (userAnswerList.isEmpty()) {
            throw new BaseException(ExceptionConstant.ANSWER_NOT_EXIST);
        }
        for (UserAnswer userAnswer : userAnswerList) {
            if (!userAnswer.getAppId().equals(app.getId())) {
                throw new BaseException(ErrorCode.FORBIDDEN_ERROR);
            }
        }
        return app;
    }
}




